/******************************************************
* sensor_ov7670_csi.c
*
* Purpose: OV7670 sensor driver for CSI path
*
* Author: Eugene Hsu
*
* Date: 2014/08/25
*
* Copyright Generalplus Corp. ALL RIGHTS RESERVED.
*
* Version :
* History :
*
*******************************************************/
/*******************************************************
	Include file
*******************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "driver_l2.h"
#include "gplib_print_string.h"

#if (defined _SENSOR_OV7670_CSI) && (_SENSOR_OV7670_CSI == 1)

/**************************************************************************
 *                           C O N S T A N T S                            *
 **************************************************************************/
#define OV7670_ID		0x42

/**************************************************************************
 *                          D A T A    T Y P E S                          *
 **************************************************************************/
/*
typedef struct regval8_s
{
	INT8U	reg_num;
	INT8U	value;
} regval8_t;
*/
/**************************************************************************
 *                         G L O B A L    D A T A                         *
 **************************************************************************/

#if SENSOR_I2C_MODE == SENSOR_I2C_MODE_SW
sccb_config_t ov7670_handle =
{
	SENSOR_I2C_SCL,		//scl_port
	SENSOR_I2C_DRIVING,	//scl_drv
	SENSOR_I2C_SDA,		//sda_port
	SENSOR_I2C_DRIVING,	//sda_drv
	0,			//pwdn_port
	0,			//pwdn_drv
	0,			//have_pwdn
	8,			//RegBits
	8,			//DataBits
	OV7670_ID,	//slaveAddr
	0x20000,	//timeout
	100			//clock_rate
};
#elif SENSOR_I2C_MODE == SENSOR_I2C_MODE_HW
static drv_l1_i2c_bus_handle_t	ov7670_handle =
{
	SENSOR_I2C_DEV,
	OV7670_ID,
	100
};
#endif

/************************
 MCLK= 24Mhz;
 PCLK=   Mhz
 Frame width= 640
 Frame Height= 480
*************************/
/* 300K YUV 15fps */
static const regval8_t OV7670_YUV_CONFIG[] =
{
	{ 0x12, 0x80 },

	// Reset all registers to default values
	{ 0x12, 0x80 },

	// Reset all registers to default values
	{ 0x12, 0x80 },

	// Reset all registers to default values
	{ 0x2d, 0x40 },

	// dummy byte
	{ 0x2e, 0x00 },
	{ 0x11, 0x02 },

	// 30fps
	//{0x11, 0x04},	// 27fps
	//{0x11, 0x06},	// 14.5fps
	//{0x11, 0x09},	// 13.5fps
	//{ 0x11, 0x08 },
	// 15fps
	//{0x11, 0x06},	// 10fps
	//{0x11, 0x09},	// 6.7fps
	{ 0x3a, 0x04 },
	{ 0x12, 0x00 },
	{ 0x17, 0x13 },
	{ 0x18, 0x01 },
	{ 0x32, 0xb6 },
	{ 0x19, 0x02 },
	{ 0x1a, 0x7a },
	{ 0x03, 0x0a },
	{ 0x0c, 0x00 },
	{ 0x3e, 0x00 },
	{ 0x70, 0x3a },
	{ 0x71, 0x35 },
	{ 0x72, 0x11 },
	{ 0x73, 0xf0 },
	{ 0xa2, 0x02 },
	{ 0x7a, 0x24 },
	{ 0x7b, 0x04 },
	{ 0x7c, 0x0a },
	{ 0x7d, 0x17 },
	{ 0x7e, 0x32 },
	{ 0x7f, 0x3f },
	{ 0x80, 0x4c },
	{ 0x81, 0x58 },
	{ 0x82, 0x64 },
	{ 0x83, 0x6f },
	{ 0x84, 0x7a },
	{ 0x85, 0x8c },
	{ 0x86, 0x9e },
	{ 0x87, 0xbb },
	{ 0x88, 0xd2 },
	{ 0x89, 0xe5 },
	{ 0x13, 0xe0 },
	{ 0x00, 0x00 },
	{ 0x10, 0x00 },
	{ 0x0d, 0x40 },
	{ 0x14, 0x18 },
	{ 0xa5, 0x02 },
	{ 0xab, 0x03 },
	{ 0x24, 0x95 },
	{ 0x25, 0x33 },
	{ 0x26, 0xe3 },
	{ 0x9f, 0x78 },
	{ 0xa0, 0x68 },
	{ 0xa1, 0x03 },
	{ 0xa6, 0xd8 },
	{ 0xa7, 0xd8 },
	{ 0xa8, 0xf0 },
	{ 0xa9, 0x90 },
	{ 0xaa, 0x94 },
	{ 0x13, 0xe5 },
	{ 0x0e, 0x61 },
	{ 0x0f, 0x4b },
	{ 0x16, 0x02 },
	{ 0x1e, 0x3f },

	//Flip & Mirror
	{ 0x21, 0x02 },
	{ 0x22, 0x91 },
	{ 0x29, 0x07 },
	{ 0x33, 0x0b },
	{ 0x35, 0x0b },
	{ 0x37, 0x1d },
	{ 0x38, 0x71 },
	{ 0x39, 0x2a },
	{ 0x3c, 0x78 },
	{ 0x4d, 0x40 },
	{ 0x4e, 0x20 },
	{ 0x69, 0x00 },

	/* 30FPS */
	{ 0x6b, 0x8a },

	// pclk*6
	//{ 0x2d, 0x40 },
	// dummy byte
	//{ 0x2e, 0x00 },
	/* 27 FPS */
	//{0x6b, 0xca},	// pclk*8
	/* 15 FPS */
	//{0x6b, 0xca},	// pclk*8
	/* 10 FPS */
	//{0x6b, 0x4a},	// pclk*4
	/* 7 FPS */
	//{0x6b, 0x4a},	// pclk*4
	{ 0x74, 0x10 },
	{ 0x8d, 0x4f },
	{ 0x8e, 0x00 },
	{ 0x8f, 0x00 },
	{ 0x90, 0x00 },
	{ 0x91, 0x00 },
	{ 0x96, 0x00 },
	{ 0x9a, 0x80 },
	{ 0xb0, 0x84 },
	{ 0xb1, 0x0c },
	{ 0xb2, 0x0e },
	{ 0xb3, 0x82 },
	{ 0xb8, 0x0a },
	{ 0x43, 0x0a },
	{ 0x44, 0xf0 },
	{ 0x45, 0x44 },
	{ 0x46, 0x7a },
	{ 0x47, 0x27 },
	{ 0x48, 0x3c },
	{ 0x59, 0xbc },
	{ 0x5a, 0xde },
	{ 0x5b, 0x54 },
	{ 0x5c, 0x8a },
	{ 0x5d, 0x4b },
	{ 0x5e, 0x0f },
	{ 0x6c, 0x0a },
	{ 0x6d, 0x55 },
	{ 0x6e, 0x11 },
	{ 0x6f, 0x9e },
	{ 0x6a, 0x40 },
	{ 0x01, 0x40 },
	{ 0x02, 0x40 },
	{ 0x13, 0xe7 },
	{ 0x4f, 0x80 },
	{ 0x50, 0x80 },
	{ 0x51, 0x00 },
	{ 0x52, 0x22 },
	{ 0x53, 0x5e },
	{ 0x54, 0x80 },
	{ 0x58, 0x9e },
	{ 0x62, 0x08 },
	{ 0x63, 0x8f },
	{ 0x65, 0x00 },
	{ 0x64, 0x08 },
	{ 0x94, 0x08 },
	{ 0x95, 0x0c },
	{ 0x66, 0x05 },
	{ 0x41, 0x08 },
	{ 0x3f, 0x00 },
	{ 0x75, 0x05 },
	{ 0x76, 0xe1 },
	{ 0x4c, 0x00 },
	{ 0x77, 0x01 },
	{ 0x3d, 0xc2 },
	{ 0x4b, 0x09 },
	{ 0xc9, 0x60 },
	{ 0x41, 0x38 },
	{ 0x56, 0x40 },
	{ 0x34, 0x11 },
	{ 0x3b, 0xca },

	//enable night mode
	//{0x3b, 0x4a},	//disable night mode
	{ 0xa4, 0x88 },
	{ 0x96, 0x00 },
	{ 0x97, 0x30 },
	{ 0x98, 0x20 },
	{ 0x99, 0x30 },
	{ 0x9a, 0x84 },
	{ 0x9b, 0x29 },
	{ 0x9c, 0x03 },
	{ 0x9d, 0x98 },
	{ 0x9e, 0x7f },
	{ 0x78, 0x04 },
	{ 0x79, 0x01 },
	{ 0xc8, 0xf0 },
	{ 0x79, 0x0f },
	{ 0xc8, 0x00 },
	{ 0x79, 0x10 },
	{ 0xc8, 0x7e },
	{ 0x79, 0x0a },
	{ 0xc8, 0x80 },
	{ 0x79, 0x0b },
	{ 0xc8, 0x01 },
	{ 0x79, 0x0c },
	{ 0xc8, 0x0f },
	{ 0x79, 0x0d },
	{ 0xc8, 0x20 },
	{ 0x79, 0x09 },
	{ 0xc8, 0x80 },
	{ 0x79, 0x02 },
	{ 0xc8, 0xc0 },
	{ 0x79, 0x03 },
	{ 0xc8, 0x40 },
	{ 0x79, 0x05 },
	{ 0xc8, 0x30 },
	{ 0x79, 0x26 },

//		{0x3b, 0x00},	// 60Hz = 0x00, 50Hz = 0x08
	{ 0x3b, 0x08 },

	// 60Hz = 0x00, 50Hz = 0x08
//		{0x6b, 0x4a},
//		{0x6b, 0x0a},
	/*
		{0x11, 0x81},	// 15fps
		{0x6b, 0x0a},
		{0x92, 0x40},
		{0x9d, 0x55},
		{0x9e, 0x47},
		{0xa5, 0x02},
		{0xab, 0x03},
*/
	{ 0xFF, 0xFF }
};

static void ov7670_sccb_open(void)
{
	#if SENSOR_I2C_MODE == SENSOR_I2C_MODE_HW
	drv_l1_i2c_init(&ov7670_handle);
	#endif
}

static void ov7670_sccb_close(void)
{
	#if SENSOR_I2C_MODE == SENSOR_I2C_MODE_HW
	drv_l1_i2c_uninit(&ov7670_handle);
	#endif
}

static INT32S ov7670_sccb_write(INT8U reg, INT8U value)
{
	#if SENSOR_I2C_MODE == SENSOR_I2C_MODE_SW
	return drv_l2_sccb_write(&ov7670_handle, reg, value);
	#elif SENSOR_I2C_MODE == SENSOR_I2C_MODE_HW
	return drv_l1_reg_1byte_data_1byte_write(&ov7670_handle, reg, value);
	#endif
}

#if 1
static INT32S ov7670_sccb_read(INT8U reg, INT8U *value)
{
#if SENSOR_I2C_MODE == SENSOR_I2C_MODE_SW
	INT16U data;

	if(drv_l2_sccb_read(&ov7670_handle, reg, &data) >= 0) {
		*value = (INT8U)data;
		return STATUS_OK;
	} else {
		*value = 0xFF;
		return STATUS_FAIL;
	}
#elif SENSOR_I2C_MODE == SENSOR_I2C_MODE_HW
	INT8U data[1];

	data[0] = reg;
	if(drv_l1_i2c_bus_write(&ov7670_handle, data, 1) < 0) {
		*value = 0xFF;
		return STATUS_FAIL;
	}

	if(drv_l1_i2c_bus_read(&ov7670_handle, data, 1) < 0) {
		*value = 0xFF;
		return STATUS_FAIL;
	}

	*value = data[0];
#endif
	return STATUS_OK;
}


#endif
static INT32S ov7670_sccb_write_table(regval8_t *pTable)
{
	I2C_Mode	mode = GP_HW_I2C;
	regval8_t	*pTable1;
	INT8U rd_data;
	INT8S transmitcnt = 0;

	pTable1 = pTable;

	while(1)
	{
		if(pTable->reg_num == 0xFF && pTable->value == 0xFF)
		{
		// 0xc & 0
		//if(pTable->reg_num == 0x89 && pTable->value == 0xe5)
		//{
			#if 1
			break;
			#else
			pTable = pTable1;    // debug iic issue
			#endif
		}

		if(mode == NORMAL_HW_I2C)
		{
			DBG_PRINT("addr=0x%x data = 0x%x  \r\n",pTable->reg_num,pTable->value);
			if(ov7670_sccb_write(pTable->reg_num, pTable->value) < 0)
			{
				DBG_PRINT("sccb write fail.\r\n");
				//while(1);
				continue;
			}
		}
		else
		{
			transmitcnt = drv_l1_i2c_group_write(&ov7670_handle,I2C_MISC_2BYTE_NORMAL, (void*)&pTable,1);
		}

#if 1
		ov7670_sccb_read(pTable->reg_num,&rd_data);

		#if 0
		DBG_PRINT("addr=0x%x w_data = 0x%x r_data=0x%x \r\n",pTable->reg_num,pTable->value,rd_data);
		#else
		if(rd_data!= pTable->value)
		{
			DBG_PRINT("addr=0x%x w_data = 0x%x r_data=0x%x \r\n",pTable->reg_num,pTable->value,rd_data);
		}
		#endif
#endif
		//pTable = pTable + transmitcnt;
		pTable++;
	}

	#if 1
	pTable =  pTable1;
	while(1)
	{
		if(pTable->reg_num == 0xFF && pTable->value == 0xFF)
		{
			break;
		}

		ov7670_sccb_read(pTable->reg_num,&rd_data);
		DBG_PRINT("addr=0x%x w_data = 0x%x r_data=0x%x \r\n",pTable->reg_num,pTable->value,rd_data);
		pTable++;
	}
	#endif

	return STATUS_OK;

/*
	while(1)
	{
		drv_l1_sys_wdt_clear();
		if(pTable->reg_num == 0xFF && pTable->value == 0xFF)
		{
			break;
		}

		if(ov7670_sccb_write(pTable->reg_num, pTable->value) < 0)
		{
			ov7670_sccb_read(pTable->reg_num);

			DBG_PRINT("sccb write fail\r\n");
			continue;
		}

		pTable++;
	}

	return STATUS_OK;
	*/
}


/**
 * @brief   ov7670 initialization function
 * @param   sensor format parameters
 * @return 	none
 */
void ov7670_csi_init(void)
{
	/* Turn on LDO 2.8V for CSI sensor */
	//drv_l1_power_ldo_28_ctrl(1, LDO_LDO28_2P8V);
	drv_l1_csi_init(SENSOR_CSI_DEV);

	/* mclk output */
	drv_l2_sensor_set_mclkout(ov7670_sensor_csi_ops.info[0].mclk, MASTER_CLOCK_MIPI_KEEP, MASTER_CLOCK_CSI_ON);

	/* request SCCB */
	ov7670_sccb_open();
	DBG_PRINT("Sensor OV7670 csi interface init completed\r\n");
}

/**
 * @brief   ov7670 un-initialization function
 * @param   sensor format parameters
 * @return 	none
 */
void ov7670_csi_uninit(void)
{
	// disable mclk
	drv_l2_sensor_set_mclkout(MCLK_NONE, MASTER_CLOCK_MIPI_OFF, MASTER_CLOCK_CSI_OFF);

	drv_l1_csi_stop(SENSOR_CSI_DEV);

	// release SCCB
	ov7670_sccb_close();

	// Turn off LDO 2.8V for CSI sensor
	//drv_l1_power_ldo_28_ctrl(0, LDO_LDO28_2P8V);
}

/**
 * @brief   ov7670 stream start function
 * @param   info index
 *
 * @return 	none
 */
void ov7670_csi_stream_start(INT32U index, INT32U bufA, INT32U bufB)
{
	/* enable CSI output clock for SCCB */
	//drv_l2_sensor_set_mclkout(ov7670_sensor_csi_ops.info[index].mclk);
	drv_l1_csi_set_clock(MASTER_CLOCK_24MHZ, MASTER_CLOCK_MIPI_KEEP, MASTER_CLOCK_CSI_ON);

	/* set start frame buffer address */
	if(bufA)
	{
		drv_l1_csi_set_buf(SENSOR_CSI_DEV, bufA);
	}
	else
	{
		DBG_PRINT("Input frame buffer address error, fail to start\r\n");
		ov7670_sensor_csi_ops.stream_stop();
	}

	drv_l1_csi_input_set(SENSOR_CSI_DEV, ov7670_sensor_csi_ops.info[index].interface_mode, ov7670_sensor_csi_ops.info[index].interlace_mode, 0, ov7670_sensor_csi_ops.info[index].input_format);

	/* Set Horizontal/Vertical counter reset, Vertical counter incresase selection, sensor owner inerrupt enable, capture/preview mode */
	drv_l1_csi_input_latch_timing1_set(SENSOR_CSI_DEV, ov7670_sensor_csi_ops.info[index].hoffset, ov7670_sensor_csi_ops.info[index].voffset, 0x00, ENUM_CSI_LATCH_DELAY_1_CLOCK);
	drv_l1_csi_input_latch_timing2_set
	(
		SENSOR_CSI_DEV,
		ov7670_sensor_csi_ops.info[index].hsync_active,
		ov7670_sensor_csi_ops.info[index].vsync_active,
		ov7670_sensor_csi_ops.info[index].vsync_active,
		ov7670_sensor_csi_ops.info[index].hsync_active
	);
	drv_l1_csi_output_format_set(SENSOR_CSI_DEV, ov7670_sensor_csi_ops.info[index].output_format);
	drv_l1_csi_output_fifo_set(SENSOR_CSI_DEV, ENUM_CSI_FIFO_DISABLE);

	/* Set sensor's registers via SCCB */
	if(ov7670_sccb_write_table((regval8_t *) OV7670_YUV_CONFIG) < 0)
	{
		DBG_PRINT("ov7670 init fail!!!\r\n");
	}

	DBG_PRINT("%s = %d\r\n", __func__, index);

	drv_l1_csi_input_resolution_set(SENSOR_CSI_DEV, ov7670_sensor_csi_ops.info[index].sensor_w, ov7670_sensor_csi_ops.info[index].sensor_h);

	if((ov7670_sensor_csi_ops.info[index].sensor_w > ov7670_sensor_csi_ops.info[index].target_w) || (ov7670_sensor_csi_ops.info[index].sensor_h > ov7670_sensor_csi_ops.info[index].target_h))
	{
		drv_l1_csi_set_scale
		(
			SENSOR_CSI_DEV,
			ov7670_sensor_csi_ops.info[index].sensor_w,
			ov7670_sensor_csi_ops.info[index].target_w,
			ov7670_sensor_csi_ops.info[index].sensor_h,
			ov7670_sensor_csi_ops.info[index].target_h
		);
	}

	/* csi long burst width need 32 align */
	if(ov7670_sensor_csi_ops.info[index].target_w & 0x1F)
	{
		drv_l1_csi_set_long_burst_mode(SENSOR_CSI_DEV, DISABLE);
	}
	else
	{
		drv_l1_csi_set_long_burst_mode(SENSOR_CSI_DEV, ENABLE);
	}

	/* Enable interrupt */
	drv_l1_csi_set_irq(SENSOR_CSI_DEV, ENABLE);

	drv_l1_csi_start(SENSOR_CSI_DEV);

}

/**
 * @brief   ov7670 stream stop function
 * @param   none
 * @return 	none
 */
void ov7670_csi_stream_stop(void)
{
	/* Disable interrupt */
	drv_l1_csi_set_irq(SENSOR_CSI_DEV, DISABLE);

	drv_l1_csi_stop(SENSOR_CSI_DEV);
}

/**
 * @brief   ov7670 get info function
 * @param   none
 * @return 	pointer to sensor information data
 */
drv_l2_sensor_info_t *ov7670_csi_get_info(INT32U index)
{
	if(index > (MAX_INFO_NUM - 1))
	{
		return NULL;
	}
	else
	{
		return (drv_l2_sensor_info_t *) &ov7670_sensor_csi_ops.info[index];
	}
}

/*********************************************
*	sensor ops declaration
*********************************************/
const drv_l2_sensor_ops_t ov7670_sensor_csi_ops =
{
	SENSOR_SOURCE_DVP,
	SENSOR_PATH_CSI,
	ov7670_csi_init,
	ov7670_csi_uninit,
	ov7670_csi_stream_start,
	ov7670_csi_stream_stop,
	NULL,
	NULL,
	NULL,
	ov7670_csi_get_info,
	{
		/* 1nd info */
		{
			MASTER_CLOCK_24MHZ,	/* CSI clock */
			CSI_IN_YUYV,		/* input format */
			CSI_OUT_YUYV,		/* output format */
			CSI_SENSOR_FPS_30,	/* FPS in sensor */
			WIDTH_640,			/* target width */
			HEIGHT_240,			/* target height */
			WIDTH_640,			/* sensor width */
			HEIGHT_480,			/* sensor height */
			0,					/* sensor h offset */
			0,					/* sensor v offset */
			CSI_HREF,			/* input interface */
			CSI_NON_INTERLACE,	/* interlace mode */
			MODE_ACTIVE_HIGH,	/* hsync pin active level */
			MODE_ACTIVE_LOW,	/* vsync pin active level */
		}
	}
};
#endif //(defined _SENSOR_OV7670_CSI) && (_SENSOR_OV7670_CSI == 1)
